/***************************************************************
 * Name:      EventRelayDll.cpp
 * Purpose:   Code for SketchUp Ruby Extension EventRelay
 * Author:    pecan Heber
 * Created:   2008/10/21
 * Copyright: pecan Heber
 * License:   GPL
 **************************************************************/
/*
	This file is part of Eventrelay, a Ruby extension for SketchUp
	Copyright (C) 2008,2009 Pecan Heber

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

// ----------------------------------------------------------------------------
// Changes to Ruby config.h to compile with MINGW32 compiler
// ----------------------------------------------------------------------------
//
// If you have the following lines at the top of \lib\ruby\1.8\i386-mswin32\config.h
// comment them out.
//
// //conficting definition with mingw
// //#if _MSC_VER != 1200
// //#error MSC version unmatch
// //#endif
//
// In config.h find the line "#define HAVE_FSYNC 1" and change it to
//
// //conficting definition with mingw
// #if !defined(HAVE_FSYNC)
//     #define HAVE_FSYNC 1
// #endif
//
// Add HAVE_ISINF to the compiler defines
//

#include "wx/wx.h"
#include "wx/msw/private.h"

#include "EventRelayDll.h"
#include "RubyClass.h"
#include "HiddenFrame.h"
#include "Debugging.h"

// ----------------------------------------------------------------------------
// Prototypes
// ----------------------------------------------------------------------------
LRESULT CALLBACK KeyboardHook(int nCode,WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK MouseHook(int nCode,WPARAM wParam, LPARAM lParam);

#define MAPVK_VK_TO_VSC 0
#define MAPVK_VSC_TO_VK 1

extern "C" DLLFUNCTIONS_API void DLLFunction(HWND);       //*testing only*
extern "C" DLLFUNCTIONS_API void TestReport(HWND handle); //*testing only*

//UINT PostWxEvent(const UINT Message, const WPARAM wParam, const LPARAM lParam);

// ----------------------------------------------------------------------------
//  Globals
// ----------------------------------------------------------------------------
int     g_DllIsInitialized = 0;             //guard while initializing
char*   g_lpszTitleToFind = " - SketchUp"; //Title of window to find
char*   g_lpszClassToFind = "AfxFrameOrView"; //Class of View window
HWND    g_hWndSketchUp = 0;                 //handle of SketchUp window
HWND    g_hWndSketchUpView = 0;             //handle of SketchUp View subwindow
HWND    g_hWndSketchUpStatusBar = 0;          //handle of SketchUp StatusBar
DWORD   g_dwCurrentProcessId = 0;           //PID of SketchUp process
HHOOK   g_hhookKbd = 0;                     //handle of Keyboard hook
HHOOK   g_hhookMouse = 0;                   //handle of mouse hook
bool    g_bKill_WM_HELP = 0;                //suppress WM_HELP when F1 suppressed
LONG    OldWndProc = 0;                     //Main windoww proc that we subclassed
HMENU   g_MenuActiveHandle = 0;             //Active menu handle or zero
// ----------------------------------------------------------------------------
//  pointers to instantiations of classes used by Dll
// ----------------------------------------------------------------------------
//  pointer to one and only RubyClass object
RubyClassHandler* EventRelayApp::pRubyClassHandler = 0;
class HiddenFrame;
EventRelayApp*  pApp = 0;               //"this" pointer for EventRelayApp class calls
HiddenFrame*    pHiddenFrame = 0;       //wxWidgets frame
Debugging*      pMyLog = 0;             //debugging log
wxLog*          pOldLog = 0;            //log to restore on exit
Debugging*      pVersion = 0;           //access to version string
// ----------------------------------------------------------------------------
// We use IMPLEMENT_APP_NO_MAIN so we can start the app from DllMain
// as we don't have a WinMain or main entry point
//
    IMPLEMENT_APP_NO_MAIN(EventRelayApp)
    // ----------------------------------------------------------------------------
    //  Events intercept table
    // ----------------------------------------------------------------------------
    BEGIN_EVENT_TABLE(EventRelayApp, wxApp)
            //EVT_BUTTON(123,EventRelayApp::OnButton) //testing only
            //EVT_CLOSE(MyEvents::OnClose) unused; CB wizard generated
    END_EVENT_TABLE()

// ----------------------------------------------------------------------------
//  DllMain
// ----------------------------------------------------------------------------
extern "C"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
// ----------------------------------------------------------------------------
{
    switch (ul_reason_for_call)
    {

        case DLL_PROCESS_ATTACH:
        {
            if ( g_DllIsInitialized ++ )
                return true; //Initialize only once per process

            wxSetInstance((HINSTANCE)hModule);

            //MessageBox( 0, "This is Dll EventRelay initializing", "EventRelay", MB_OK);
            int argc = 0;
            char **argv = NULL;

            if (not wxEntryStart(argc, argv))
            {
                MessageBox( 0, "EventRelay:wxEntryStart did not initialize", "EventRelay", MB_OK);
                return FALSE;
            }
            if ( !wxTheApp || !wxTheApp->CallOnInit() ) // calls OnInit() below
            {
                MessageBox( 0, "EventRelay:wxTheApp did not initialize", "EventRelay", MB_OK);
                return FALSE;
            }

            // wxTheApp is allocated, we must not return a FALSE
            // or we'll crash during DLL_PROCESS_DETACH
            if ((not pApp) || (not pApp->DllProcessAttach()) )
            {
                MessageBox( 0, "EventRelay:DLL_PROCESS_ATTACH did not initialize", "EventRelay", MB_OK);
                //return FALSE; <<-- causes crash in DLL_PROCESS_DETACH/wxEntryCleanup()
                return TRUE;
                // Although we return TRUE, the g_DllIsInitialized has not been
                // updated. So hooks and subclassing are disabled.
            }

            //allow hooks to process
            g_DllIsInitialized += 1;

            break;

        }//DLL_PROCESS_ATTACH

        case DLL_THREAD_ATTACH:
            break;

        case DLL_THREAD_DETACH:
            break;

        case DLL_PROCESS_DETACH:
        // The MSDN docs say to *not* deallocate on pgm termination. Just
        // to let the system reclaim the memory.
        //if ( 0 == lpReserved)  //Dll being explicitly unloaded
        do{
            // Remove subclassing and then do cleanup
            UnhookWindowsHookEx( g_hhookKbd);
            UnhookWindowsHookEx( g_hhookMouse);

            #if defined(LOGGING)
                if (pMyLog)
                {   pMyLog->GetLog()->Flush();
                    //wxLog::SetActiveTarget(pMyLog->GetOldLog());
                    pMyLog->GetLog()->SetActiveTarget(NULL);
                    wxLog::SetActiveTarget(NULL);
                    pMyLog->GetLog()->GetFrame()->Destroy();
                    //-pMyLog->GetFrame()->Close();   <== worthless call
                    //wxLog::SetActiveTarget(NULL);
                    pMyLog = 0;
                }
            #endif

            // restore the main windows old wndProc
            if (g_hWndSketchUp && OldWndProc)
                SetWindowLong(g_hWndSketchUp, GWL_WNDPROC, (long)OldWndProc);

            if (pHiddenFrame)
            {
                wxTheApp->SetTopWindow(0);
                pHiddenFrame->Destroy();
            }

            wxTheApp->OnExit();
            //-wxTheApp->CleanUp(); causes crash deleting objects
            //-wxEntryCleanup(); causes crash deleting objects

        }while(0);
        break;
    }
    return TRUE;
}
// ----------------------------------------------------------------------------
extern "C" DLLFUNCTIONS_API void Init_EventRelay(void)
// ----------------------------------------------------------------------------
{//This routine must remain global extern "C" or it won't get called

    // This init function is called by Ruby when this Dll is loaded
    // (Ruby calls it an extension).
    // as.../SketchUp/Plugins/EventRelay.dll

    // A simple EventRelay.rb contains a 'require "EventRelay.dll"' statement

    // This function called after DLL initialization DLL_PROCESS_ATTACH
    // and wxWidgets OnInit() routine.

    // sanity tests
    if (g_DllIsInitialized < 2) return;
    //-if (0 == pHiddenFrame) return;

    // Create RubyClassHandler to handle calls from the user script
    // and for callbacks to the ruby script
    // Set some globals needed by the RubyClassHandler
    EventRelayApp::pRubyClassHandler = new RubyClassHandler;

    EventRelayApp::pRubyClassHandler->SetHiddenFrame( (HiddenFrame*)pHiddenFrame );
    ((HiddenFrame*)pHiddenFrame)->SetRubyClassHandler(EventRelayApp::pRubyClassHandler);

    if(EventRelayApp::pRubyClassHandler)
    {
        #if defined(LOGGING)
        LOGIT( _T("Init_EventRelay invoked from Ruby 'require'"));
        #endif
    }
    return;
}
// ----------------------------------------------------------------------------
EventRelayApp::EventRelayApp()
// ----------------------------------------------------------------------------
{
    //ctor
    pApp = this;
}
// ----------------------------------------------------------------------------
EventRelayApp::~EventRelayApp()
// ----------------------------------------------------------------------------
{
    //dtor
    ;
}
// ----------------------------------------------------------------------------
bool EventRelayApp::OnInit()
// ----------------------------------------------------------------------------
{
    // Called by wxApp->CallOnInit;
    // return true here or the wxApp will exit and we won't cleanup
    // properly when DLL_PROCESS_DETACH is called.

    pApp = this;

    return true;
}
// ----------------------------------------------------------------------------
bool EventRelayApp::DllProcessAttach()
// ----------------------------------------------------------------------------
{
    // Called by DLL_PROCESS_ATTACH and
    // called first; before Ruby's Init_EventRelay() routine

    pRubyClassHandler = 0;

    //Create a dummy hidden window so wxWidgets behaves
    if (not CreateHiddenFrame() )
    {
        MessageBox( 0, "EventRelay: failed to create internal window", "EventRelay", MB_OK);
        return FALSE;
    }

    #if defined(LOGGING)
     CreateDebuggingLog();
    #endif
    if (pMyLog)
        pVersion = pMyLog;
    else
        pVersion = new Debugging("NoLogging");

    //Testing: *Deprecated*
    //pHiddenFrame->Show(true); //debugging: show hidden frame

    // Find main program window and subclass it. We want to see windows
    // events before SketchUp does.
    // Hook the keyboard and mouse to get raw events
    if (SubClassMainWindow(0))
    {
        g_hhookKbd = SetWindowsHookEx(WH_KEYBOARD, KeyboardHook, 0, GetCurrentThreadId());
        g_hhookMouse = SetWindowsHookEx(WH_MOUSE, MouseHook, 0, GetCurrentThreadId());
        if ( g_hhookKbd && g_hhookMouse )
        {
            #if defined(LOGGING)
            LOGIT( _T("EventRelay Hooking & Creation of SubClassing Done."));
            #endif
        }
        else
        {
            if (g_hhookKbd == NULL)
                wxMessageBox("EventRelay DLL Keyboard hook failed");
            if (g_hhookMouse == NULL)
                wxMessageBox("EventRelay DLL Mouse hook failed");
            // Remove subclassing and then do cleanup
            UnhookWindowsHookEx( g_hhookKbd);
            UnhookWindowsHookEx( g_hhookMouse);
            return FALSE;
        }
    }
    else
    {
        MessageBox( 0, "EventRelay:Creation of SubClassing failed", "EventRelay", MB_OK);
        return false;
    }

    return true;
}
// ----------------------------------------------------------------------------
LRESULT CALLBACK NewWndProc(HWND Hwnd, UINT message, WPARAM wParam, LPARAM lParam)
// ----------------------------------------------------------------------------
{
    // This is the windows message loop that gets to see WM_messages
    // just before SketchUp does.

    LRESULT rc = 0;

    // Writing to the log here during our initialization causes crashes
    if (g_DllIsInitialized < 2)
         goto ContinueProcessing;

    switch(message)
    {
        //we use a keyboard hook instead of this proc for WM_KEYDOWN
        // and WM_KEYUP in order to see all the keys. Even Alt and Win keys.
        //-case WM_KEYDOWN: {break;}
        //-case WM_KEYUP{break;}
        //case WM_SYSKEYDOWN:
        //case WM_SYSKEYUP:
        //{
        //    return true;
        //}

        case WM_HELP:
        {   // Special processing for F1 key
            if (g_bKill_WM_HELP)
            {
                g_bKill_WM_HELP = false;
                return true;
            }
            break;
        }
        case  WM_COMMAND:
        {
            #if defined(LOGGING)
             UINT wParamHigh = HIWORD(wParam);
             UINT wParamLow  = LOWORD(wParam);
             LOGIT( _T("NEWWND_PROC WM_COMMAND MenuType[%d]id[%d]"), wParamHigh, wParamLow);
            #endif
            //rc = pApp->PostWxEvent( message, wParam, lParam);
            rc = pApp->OnMenuSelected( message, wParam, lParam);
            if (rc) return true;
            break;
        }
        case WM_INITMENUPOPUP:
        {
            // sent when a drop-down menu or submenu is about to become active.
            //    wParam
            //        Handle to the drop-down menu or submenu.
            //    lParam
            //        The low-order word specifies the zero-based relative position of the menu item that opens the drop-down menu or submenu.
            //
            //        The high-order word indicates whether the drop-down menu is the window menu. If the menu is the window menu, this parameter is TRUE; otherwise, it is FALSE.
            //
            //    Return Value
            //        If an application processes this message, it should return zero.
            #if defined(LOGGING)
            LOGIT( _T("WM_INITMENUPOPUP handle[%p] posn[%d] mainMenu[%d]"),
                        (void*)wParam, LOWORD(lParam), HIWORD(lParam));
            #endif
            g_MenuActiveHandle = (HMENU)wParam;
            break;
        }

        case WM_MENUSELECT:
        {
            //sent to a menu's owner window when the user selects a menu item.
            HMENU hMenu = (HMENU)lParam;
            g_MenuActiveHandle = hMenu;
            //-if (not hMenu) break;
            char menuString[80] = {0};
            int id = LOWORD(wParam);
            int flags = HIWORD(wParam);
            int itemType = (flags & MF_POPUP) ? MF_BYPOSITION : MF_BYCOMMAND;
            int len = GetMenuString( hMenu, id, menuString, 79, itemType );
            if ( len ) {;}
            #if defined(LOGGING)
            LOGIT( _T("WM_MENUSELECT ID/Pos[%d] flags[%d] handle[%p] label[%s]"),
                        LOWORD(wParam),HIWORD(wParam), (void*)lParam, menuString );
            #endif
            if ( 0 == g_MenuActiveHandle  )
            {
                pHiddenFrame->SendMenuActionsNow();
            }
            break;
        }

        case WM_CONTEXTMENU:
        {   // This msg is generated by this DLL onRMouseDown event
            //rc = pApp->PostWxEvent( message, wParam, lParam );
            #if defined(LOGGING)
            LOGIT( _T("[%s]"), _T("WM_CONTEXTMENU"));
            #endif
            break;
        }

        case WM_MENUCOMMAND:
        {   // we don't get any of these
            //send when the user selects a command item from a menu,
            #if defined(LOGGING)
            LOGIT( _T("WM_MENUCOMMAND[%d]"), message);
            #endif
            //rc = PostWxEvent( message, wParam, lParam);
            //if (rc) return true;
            break;
        }

        default: break;
    }//switch

    if (rc) return true; //message was processed

    ContinueProcessing: // Pass the message onto SketchUp
    return CallWindowProc((WNDPROC)OldWndProc, Hwnd, message, wParam, lParam);
}
    //Doc: WM_COMMAND
    //  Message     wParam      wParam                          lParam
    //  Source      high word)	(low word)
    //  ---------   ----------  -----------------------         -------------
    //  Menu	    0	        Menu identifier (IDM_*)	        0
    //  Accelerator	1	        Accelerator identifier (IDM_*)	0
    //  Control	    Control-    Control                         Handle to the
    //              defined     identifier                      control window
    //              notification
    //              code
// ----------------------------------------------------------------------------
LRESULT CALLBACK KeyboardHook(int nCode,WPARAM wParam, LPARAM lParam)
// ----------------------------------------------------------------------------
{
    // If our monitored window, hand off the KeyDown and KeyUp
    // events to the HiddenWindow event processor who will pass it on
    // to the Ruby user if the user has define a method to
    // handle the event.

    // If the Ruby user has not defined a method to handle the event,
    // pass it on to the next hook user. (Most likely SketchUp)

    if ( nCode == HC_ACTION ) do
    {
        bool   rc = FALSE;
        // If not our target window, skip
        if (GetForegroundWindow() not_eq g_hWndSketchUp)
            break;
        // if a menu is active, pass it onward w/o user inspection
        if (g_MenuActiveHandle)
            break;
        #if defined(LOGGING)
        //LOGIT( _T("KEYBOARDHOOK[%d][%d][%d][%d][%lX]"),
        //        nCode, wParam, HIWORD(lParam), LOWORD(lParam), lParam );
        #endif

        UINT message = WM_KEYDOWN;
        if (lParam & 0x80000000)
            message = WM_KEYUP;

        //rc = pApp->OnKeyUpDown( message, wParam, lParam);
        rc = pApp->PostWxEvent( message, wParam, lParam );


        if ( rc ) //user is disposing of this key
        {
            // Special processing for disposal of F1 key which
            // can't be suppressed like ordinary keys.
            // so we suppress the WM_HELP message it produces.
            if (wParam == VK_F1)
                g_bKill_WM_HELP = true;
            #if defined(LOGGING)
            if (message == WM_KEYDOWN)
            LOGIT( _T("DLL SUPPRESSED msg[%s]key[%d]"),
                (message==WM_KEYDOWN) ? _T("WM_KEYDOWN":_T("WM_KEYUP")),
                 unsigned(wParam) );
            #endif
        }
        else
        {
            #if defined(LOGGING)
            if (message == WM_KEYDOWN)
            LOGIT( _T("DLL ALLOWED   msg[%s]key[%d]"),
                (message==WM_KEYDOWN) ? _T("WM_KEYDOWN":_T("WM_KEYUP")),
                 unsigned(wParam) );
            #endif
            ;
        }
        if (rc) return true;    // NoOne else gets to see this key
        break;                  // else pass the key onward.

    }while(0);

    return CallNextHookEx( g_hhookKbd, nCode, wParam, lParam);

    //Doc:
    //wParam: virtual key code
    //lParam
    //    [in] Specifies the repeat count, scan code, extended-key flag, context code,
    //      previous key-state flag, and transition-state flag. For more information
    //      about the lParam parameter, see Keystroke Message Flags. This parameter can
    //      be one or more of the following values.
    //
    //    0-15
    //        Specifies the repeat count. The value is the number of times the keystroke
    //          is repeated as a result of the user's holding down the key.
    //    16-23
    //        Specifies the scan code. The value depends on the OEM.
    //    24
    //        Specifies whether the key is an extended key, such as a function key or a
    //        key on the numeric keypad. The value is 1 if the key is an extended key;
    //        otherwise, it is 0.
    //    25-28
    //        Reserved.
    //    29
    //        Specifies the context code. The value is 1 if the ALT key is down;
    //        otherwise, it is 0.
    //    30
    //        Specifies the previous key state. The value is 1 if the key is down before
    //        the message is sent; it is 0 if the key is up.
    //    31
    //        Specifies the transition state. The value is 0 if the key is being
    //        pressed and 1 if it is being released.

}//KeyboardHook
// ----------------------------------------------------------------------------
LRESULT CALLBACK MouseHook(int nCode,WPARAM wParamIn, LPARAM lParamIn)
// ----------------------------------------------------------------------------
{
    // If the active window is our monitored window, hand off the mouse key
    // event to the HiddenWindow event processor who will pass it on
    // to the Ruby user if the user has define a method to
    // handle the event.

    // The the Ruby user has not defined a method to handle the event,
    // pass it on to the next hook user. (Most likely SketchUp)

    // The MouseProc hook procedure is an application-defined or library-defined
    // callback function used with the SetWindowsHookEx function. The system calls
    // this function whenever an application calls the GetMessage or PeekMessage
    // function and there is a mouse message to be processed.

    MOUSEHOOKSTRUCT* pMouseInfo = 0;
    UINT   message = 0;
    WPARAM wParam  = 0;
    LPARAM lParam  = 0;
    bool   rc = FALSE;

    if ( nCode == HC_ACTION ) do
    {
        #if defined(LOGGING)
        //LOGIT( _T("MOUSEHOOK[%d][%d][%d][%d]"), nCode, wParam, HIWORD(lParam), LOWORD(lParam) );
        #endif

        pMouseInfo = (PMOUSEHOOKSTRUCT)lParamIn;
        //HWND hWnd = pMouseInfo->hwnd;   <== not foreground window.
        //if (hWnd not_eq g_hWndSketchUp) <== not foreground window.
        //    break;

        // If not our target window, skip
        if (GetForegroundWindow() not_eq g_hWndSketchUp)
            break;

        // get mouse x,y into lParam
        lParam = MAKELONG(pMouseInfo->pt.x, pMouseInfo->pt.y);
        // Get the modifier states
        BYTE lpKeyState[256]={0};
        BOOL getStateOk = GetKeyboardState( lpKeyState);
        if (getStateOk)
            wParam = 0 | (lpKeyState[VK_CONTROL]  & 0x80 ? MK_CONTROL:0)
                       | (lpKeyState[VK_SHIFT]    & 0x80 ? MK_SHIFT:0)
                       | (lpKeyState[VK_LBUTTON]  & 0x80 ? MK_LBUTTON:0)
                       | (lpKeyState[VK_RBUTTON]  & 0x80 ? MK_RBUTTON:0)
                       | (lpKeyState[VK_MBUTTON]  & 0x80 ? MK_MBUTTON:0)
                       | (lpKeyState[VK_XBUTTON1] & 0x80 ? MK_XBUTTON1:0)
                       | (lpKeyState[VK_XBUTTON2] & 0x80 ? MK_XBUTTON2:0)
                       ;//| (lpKeyState[VK_MENU]  & 0x80 ? MK_MENU:0) ; Handled by common windows function

        switch (wParamIn)
        {
            case WM_RBUTTONDOWN:
            {
                #if defined(LOGGING)
                //LOGIT( _T("MouseHook WM_RBUTTON DOWN[%d] pt(%ld %ld)"), wParam, pMouseInfo->pt.x, pMouseInfo->pt.y);
                #endif
                message = WM_RBUTTONDOWN;
                rc = pApp->OnMouseUpDown( message, wParam, lParam);
                if (rc) return true;
                break;
            }
            case WM_RBUTTONUP:
                #if defined(LOGGING)
                //LOGIT( _T("MouseHook WM_RBUTTON UP[%d] pt(%ld %ld)"), wParam, pMouseInfo->pt.x, pMouseInfo->pt.y);
                #endif
                message = WM_RBUTTONUP; //Send Right Mouse up to hidden window
                rc = pApp->OnMouseUpDown( message, wParam, lParam);
               if (rc) return true;
                break;
            case WM_LBUTTONDOWN:
            {
                message = WM_LBUTTONDOWN;
                rc = pApp->OnMouseUpDown( message, wParam, lParam);
                if (rc) return true;
                break;
            }
            case WM_LBUTTONUP:
                message = WM_LBUTTONUP; //Send Right Mouse up to hidden window
                rc = pApp->OnMouseUpDown( message, wParam, lParam);
                if (rc) return true;
                break;
            case WM_MBUTTONDOWN:
            {
                message = WM_MBUTTONDOWN;
                rc = pApp->OnMouseUpDown( message, wParam, lParam);
                if (rc) return true;
                break;
            }
            case WM_MBUTTONUP:
                message = WM_MBUTTONUP; //Send Right Mouse up to hidden window
                rc = pApp->OnMouseUpDown( message, wParam, lParam);
                if (rc) return true;
                break;
        }//switch
    }while(0);

    return CallNextHookEx( g_hhookMouse, nCode, wParam, lParam);

    //Doc:
    // Parameters:
    //
    //  nCode
    //  [in] Specifies a code the hook procedure uses to determine how to process
    //  the message. If nCode is less than zero, the hook procedure must pass the
    //  message to the CallNextHookEx function without further processing and should
    //  return the value returned by CallNextHookEx. This parameter can be one of the
    //  following values.
    //
    //        HC_ACTION
    //            The wParam and lParam parameters contain information about a mouse message.
    //        HC_NOREMOVE
    //            The wParam and lParam parameters contain information about a mouse message,
    //            and the mouse message has not been removed from the message queue.
    //            (An application called the PeekMessage function, specifying the PM_NOREMOVE flag.)
    //
    //    wParam
    //        [in] Specifies the identifier of the mouse message.
    //    lParam
    //        [in] Pointer to a MOUSEHOOKSTRUCT structure.
    //          typedef struct {
    //              POINT pt;
    //              HWND hwnd;
    //              UINT wHitTestCode;
    //              ULONG_PTR dwExtraInfo;
    //          }MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT;
    //
    //          Members
    //
    //          pt
    //              Specifies a POINT structure that contains the x- and y-coordinates
    //              of the cursor, in screen coordinates.
    //          hwnd
    //              Handle to the window that will receive the mouse message corresponding
    //              to the mouse event.
    //          wHitTestCode
    //              Specifies the hit-test value. For a list of hit-test values,
    //              see the description of the WM_NCHITTEST message.
    //          dwExtraInfo
    //              Specifies extra information associated with the message.
    //
    //Return Value
    //
    //    If nCode is less than zero, the hook procedure must return the value returned
    //    by CallNextHookEx.
    //
    //    If nCode is greater than or equal to zero, and the hook procedure did not
    //    process the message, it is highly recommended that you call CallNextHookEx
    //    and return the value it returns; otherwise, other applications that have
    //    installed WH_MOUSE hooks will not receive hook notifications and may behave
    //    incorrectly as a result. If the hook procedure processed the message, it may
    //    return a nonzero value to prevent the system from passing the message to the
    //    target window procedure.
    //
    //Remarks
    //
    //    An application installs the hook procedure by specifying the WH_MOUSE hook
    //    type and a pointer to the hook procedure in a call to the SetWindowsHookEx
    //    function.
    //
    //    The hook procedure must not install a WH_JOURNALPLAYBACK Hook callback function.
    //
}//MouseHook
// ----------------------------------------------------------------------------
BOOL CALLBACK EventRelayApp::EnumWindowsProc( HWND hWnd, LPARAM lParam)
// ----------------------------------------------------------------------------
{

    // Look for a window named in variable g_lpszTitleToFind.
    // It must share our Pid, ie., be in the same process as this
    // routine. This is how we find the correct SketchUp window when
    // the system is running multiple SketchUp processes.

    // Do not depend on the return value. We return FALSE to stop
    // further callbacks after we've found the target window

    DWORD EnumWindowProcessId;
    GetWindowThreadProcessId( hWnd, &EnumWindowProcessId);
    if ( EnumWindowProcessId == g_dwCurrentProcessId )
    {   // Found one of our windows

        int nLen = GetWindowTextLength(hWnd);
        if (nLen)
        {
            LPSTR lpszTitle = new CHAR[nLen + 2];
            GetWindowText(hWnd, lpszTitle, nLen+1);
            if ( strstr( lpszTitle, g_lpszTitleToFind) )
            {
                g_hWndSketchUp = hWnd;
                #if defined(LOGGING)
                LOGIT( _T("Found target window %s"), lpszTitle);
                #endif
                EnumChildWindows(g_hWndSketchUp, EnumChildWindowsProc, 0);
                return FALSE; //done
            }
            #if defined(LOGGING)
            LOGIT( _T("Found window %s"), lpszTitle);
            #endif
            delete [] lpszTitle;
        }
    }
    return TRUE;
}
// ----------------------------------------------------------------------------
BOOL CALLBACK EventRelayApp::EnumChildWindowsProc( HWND hWnd, LPARAM lParam)
// ----------------------------------------------------------------------------
{
    // Find the first child window w/class AfxFrameOrView. This
    // will be the users drawing window and the one needed to
    // translate screen coordinates to client coordinates.

    // Look for a window with class name in g_lpszClassToFind.
    // It must share our Pid, ie., be in the same process as this
    // routine. This is how we find the correct SketchUp window when
    // the system is running multiple SketchUp processes.

    // Do not depend on the return value. We return FALSE to stop
    // further callbacks after we've found the target window

    DWORD EnumWindowProcessId;
    GetWindowThreadProcessId( hWnd, &EnumWindowProcessId);
    if ( EnumWindowProcessId == g_dwCurrentProcessId )
    {   // Found one of our child windows

        TCHAR lpszClassName[80] = {0};
        TCHAR lpszTitle[80] = {0};
        int nLen = GetWindowTextLength(hWnd);
        if (nLen)
            GetWindowText(hWnd, lpszTitle, nLen);
        nLen = ::GetClassName(hWnd, lpszClassName, 20);
        int id = GetDlgCtrlID(hWnd);
        if ( id ) {;}
        if ( nLen && strstr( lpszClassName, g_lpszClassToFind) )
        {
            g_hWndSketchUpView = hWnd;
            #if defined(LOGGING)
            LOGIT( _T("Found target window Title[%s] class[%s]"), lpszTitle, lpszClassName);
            #endif
            //-return FALSE; //done
        }
        if ( nLen && strstr( lpszClassName, _T("statusbar")) )
        {
            g_hWndSketchUpStatusBar = hWnd;
            #if defined(LOGGING)
            LOGIT( _T("Found StatusBar Title[%s] class[%s]"), lpszTitle, lpszClassName);
            #endif
            //-return FALSE; //done
        }
        #if defined(LOGGING)
        LOGIT( _T("Found CHILD window Title[%s] class[%s] ID[%d]"), lpszTitle, lpszClassName, id);
        #endif
    }
    return TRUE;
}
// ----------------------------------------------------------------------------
bool EventRelayApp::SubClassMainWindow(LPVOID)
// ----------------------------------------------------------------------------
{

    // Place our MS message loop ahead of the SketchUp window so we get
    // to see the window messages before SketchUp does.

    // Get our pid to match against enumerated windows (in case of multiple same named wins)
    g_dwCurrentProcessId = GetCurrentProcessId();
    EnumWindows( EnumWindowsProc, 0);
    HWND hWnd = g_hWndSketchUp;
    if (not hWnd)
    {
        wxMessageBox(wxString::Format(_T("EventRelay:Failed to locate %s"), g_lpszTitleToFind));
        return FALSE;
    }

    //subclass the original window procedure with our new one
    ::EnableWindow(hWnd, false);
    OldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, (long)NewWndProc);
    ::EnableWindow(hWnd, true);

     //Now all messages sent to the original pgms window, are sent to
     // OUR window proc first.
    return TRUE;
}
// ----------------------------------------------------------------------------
wxWindow* EventRelayApp::CreateHiddenFrame()
// ----------------------------------------------------------------------------
{
    // Create a wxWidgets hidden frame else wxWidgets behaves badly

    pHiddenFrame = new HiddenFrame(0, -1, "HiddenFrame");
    SetTopWindow(pHiddenFrame);

    if (not pHiddenFrame )
    {
        return 0;
    }

	return pHiddenFrame;
}
// ----------------------------------------------------------------------------
bool EventRelayApp::CreateDebuggingLog()
// ----------------------------------------------------------------------------
{
    // Create debugging log window

    // Testing: create a log window for debugging messages
    if (pMyLog) return true;
    pMyLog = new Debugging(_T("EventRelay Dll Log"));
    #if defined(LOGGING)
      LOGIT( "DLL Debug Logging initialized.");
	#endif

	return TRUE;
}
// ----------------------------------------------------------------------------
UINT EventRelayApp::PostWxEvent( const UINT Message, const WPARAM wParam, const LPARAM lParam)
// ----------------------------------------------------------------------------
{
    // Post an event to the hidden wxWidgets window which will parse it.
    // If it's an event of interest, it will get passed on to the Ruby class
    // user.

    POINT point;
    point.x = 0;
    point.y = 0;
    WXMSG wxmsg;
    wxmsg.hwnd    = 0;
    wxmsg.message = Message;
    wxmsg.wParam  = wParam;
    wxmsg.lParam  = lParam;
    wxmsg.time    = 0;
    wxmsg.pt      = point;
    LRESULT result;

    if (not pHiddenFrame) return FALSE;

    wxCommandEvent event;
    pHiddenFrame->SetProcessed( event, false);
    HWND myWinHwnd = (HWND)(pHiddenFrame->GetHandle());
    result = SendMessage( myWinHwnd, wxmsg.message, wxmsg.wParam, wxmsg.lParam);

    //Doc: result = pHiddenFrame->MSWWindowProc( Message, wParam, lParam);
    #if defined(LOGGING)
        //LOGIT( _T("PostWxEvent Processed[%d]as[%d][%s]rc[%ld]"), Message, wxmsg.message, pHiddenFrame->GetProcessed() ? _T("TRUE"):_T("FALSE"), result);
    #endif

    if ( pHiddenFrame->GetProcessed() )
        return true;

    return false;
}//PostWxEvent
// ----------------------------------------------------------------------------
UINT EventRelayApp::VirtualKeyToScanCode(UINT virtualKey)
// ----------------------------------------------------------------------------
{
    return MapVirtualKey( virtualKey,  MAPVK_VK_TO_VSC);
    //return MapVirtualKeyEx( virtualKey,  MAPVK_VK_TO_VSC, 0);
}//VirtualKeyToScanCode
// ----------------------------------------------------------------------------
UINT EventRelayApp::ScanCodeToVirtualKey(UINT scanCode)
// ----------------------------------------------------------------------------
{
    return MapVirtualKey( scanCode,  MAPVK_VSC_TO_VK);
}//VirtualKeyToScanCode

    // The MapVirtualKeyEx function translates (maps) a virtual-key code into a
    // scan code or character value, or translates a scan code into a virtual-key code.
    // The function translates the codes using the input language and an input locale
    // identifier.
    //
    // Syntax
    //
    //    UINT MapVirtualKeyEx(
    //        UINT uCode,
    //        UINT uMapType,
    //        HKL dwhkl
    //    );
    //
    // Parameters
    //
    //    uCode
    //      [in] Specifies the virtual-key code or scan code for a key. How this
    //      value is interpreted depends on the value of the uMapType parameter.
    //    uMapType
    //        [in] Specifies the translation to perform. The value of this parameter
    //        depends on the value of the uCode parameter.
    //
    //        MAPVK_VK_TO_VSC
    //            The uCode parameter is a virtual-key code and is translated into
    //            a scan code. If it is a virtual-key code that does not distinguish
    //            between left- and right-hand keys, the left-hand scan code is returned. If there is no translation, the function returns 0.
    //        MAPVK_VSC_TO_VK
    //            The uCode parameter is a scan code and is translated into a virtual-key
    //            code that does not distinguish between left- and right-hand keys.
    //            If there is no translation, the function returns 0.
    //
    //            Windows Vista and later: The high byte of the uCode value can contain
    //            either 0xe0 or 0xe1 to specify the extended scan code.
    //        MAPVK_VK_TO_CHAR
    //            The uCode parameter is a virtual-key code and is translated into
    //            an unshifted character value in the low order word of the return value.
    //            Dead keys (diacritics) are indicated by setting the top bit of the
    //            return value. If there is no translation, the function returns 0.
    //        MAPVK_VSC_TO_VK_EX
    //            Windows NT/2000/XP and later: uCode is a scan code and is translated
    //            into a virtual-key code that distinguishes between left- and
    //            right-hand keys. If there is no translation, the function returns 0.
    //
    //            Windows Vista and later: The high byte of the uCode value can contain
    //            either 0xe0 or 0xe1 to specify the extended scan code.
    //        MAPVK_VK_TO_VSC_EX
    //            Windows Vista and later: The uCode parameter is a virtual-key code
    //            and is translated into a scan code. If it is a virtual-key code that
    //            does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If the scan code is an extended scan code, the high byte of the uCode value can contain either 0xe0 or 0xe1 to specify the extended scan code. If there is no translation, the function returns 0.
    //
    //    dwhkl
    //        [in] Input locale identifier to use for translating the specified code.
    //        This parameter can be any input locale identifier previously returned
    //        by the LoadKeyboardLayout function.
    //
    // Return Value
    //
    //    The return value is either a scan code, a virtual-key code, or a character value,
    //    depending on the value of uCode and uMapType. If there is no translation,
    //    the return value is zero.
    //
    // Remarks
    //
    //    The input locale identifier is a broader concept than a keyboard layout,
    //    since it can also encompass a speech-to-text converter, an Input Method
    //    Editor (IME), or any other form of input.
    //
    //    An application can use MapVirtualKeyEx to translate scan codes to the
    //    virtual-key code constants VK_SHIFT, VK_CONTROL, and VK_MENU, and vice versa.
    //    These translations do not distinguish between the left and right instances
    //    of the SHIFT, CTRL, or ALT keys.
    //
    //    Windows NT/2000/XP: An application can get the scan code corresponding to
    //    the left or right instance of one of these keys by calling MapVirtualKeyEx
    //    with uCode set to one of the following virtual-key code constants.
    //
    //        * VK_LSHIFT
    //        * VK_RSHIFT
    //        * VK_LCONTROL
    //        * VK_RCONTROL
    //        * VK_LMENU
    //        * VK_RMENU
    //
    //    These left- and right-distinguishing constants are available to an application
    //    only through the GetKeyboardState, SetKeyboardState, GetAsyncKeyState,
    //    GetKeyState, MapVirtualKey, and MapVirtualKeyEx functions. For list complete
    //    table of virtual key codes, see Virtual-Key Codes.
    //
// ----------------------------------------------------------------------------
bool EventRelayApp::OnMenuSelected( const UINT message, const WPARAM wParam, const LPARAM lParam)
// ----------------------------------------------------------------------------
{
    // Menu selected event with WM_COMMAND message

    bool rc = false;

    do
    {
         UINT menuId = 0;
        if (not GetRubyClassHandler() )
            break;

        if ( 0 == HIWORD(wParam) )
            menuId = LOWORD(wParam);
        else break;

        wxString label = GetMSWndMenuLabel(menuId);

        #if defined(LOGGING)
        LOGIT( _T("OnMenuSelected event Handler[%d]Label[%s]"), menuId, label.c_str());
        #endif
        rc = GetRubyClassHandler()->Call_Ruby_OnMenuSelected( menuId, label );
    }while(0);

    return rc;

}
    //Doc: WM_COMMAND
    //  Message     wParam      wParam                          lParam
    //  Source      high word)	(low word)
    //  ---------   ----------  -----------------------         -------------
    //  Menu	    0	        Menu identifier (IDM_*)	        0
    //  Accelerator	1	        Accelerator identifier (IDM_*)	0
    //  Control	    Control-    Control                         Handle to the
    //              defined     identifier                      control window
    //              notification
    //              code
// ----------------------------------------------------------------------------
wxString EventRelayApp::GetMSWndMenuLabel(const unsigned menuId)
// ----------------------------------------------------------------------------
{
    // Get label from MS window menu item (not wxWindow menu)

    wxString menuLabel("");
    HMENU hMenu = GetMenu(g_hWndSketchUp);
    int iMenuItemCount = GetMenuItemCount( hMenu );

    if ( (0 == iMenuItemCount) or (-1 == iMenuItemCount) )
        return menuLabel;

        TCHAR* zBuffer = 0;
        bool rc = false;
    do{
        // Get Item Text length
        MENUITEMINFO mii;
        memset(&mii, 0, sizeof(mii));
        mii.cbSize = sizeof (MENUITEMINFO);
        mii.fMask = MIIM_STRING | MIIM_ID;
        mii.dwTypeData = NULL;

        rc = GetMenuItemInfo (hMenu, menuId, false, &mii);
        if ( false == rc) break;
        if ( 0 == mii.cch ) break; //label length is 0

        // allocate buffer from length
        TCHAR* zBuffer = new TCHAR[mii.cch+1];

        // Get menu label itself
        MENUITEMINFO mii2;
        memset(&mii2, 0, sizeof(mii));
        mii2.cbSize = sizeof (MENUITEMINFO);
        mii2.fMask = MIIM_STRING;
        mii2.dwTypeData = zBuffer;
        mii2.cch = mii.cch+1;

        rc = GetMenuItemInfo (hMenu, menuId, false, &mii2);
        if ( false == rc) break;

        menuLabel = zBuffer;

    }while(0);

    if (zBuffer) delete [] zBuffer;

    return menuLabel;
}
    //Doc
    //    typedef struct tagMENUITEMINFO {
    //      UINT      cbSize;
    //      UINT      fMask;
    //      UINT      fType;
    //      UINT      fState;
    //      UINT      wID;
    //      HMENU     hSubMenu;
    //      HBITMAP   hbmpChecked;
    //      HBITMAP   hbmpUnchecked;
    //      ULONG_PTR dwItemData;
    //      LPTSTR    dwTypeData;
    //      UINT      cch;
    //      HBITMAP   hbmpItem;
    //    } MENUITEMINFO, *LPMENUITEMINFO;
// ----------------------------------------------------------------------------
bool EventRelayApp::OnMouseUpDown(const UINT message, const WPARAM wParam, const LPARAM lParam )
// ----------------------------------------------------------------------------
{
    // Middle Mouse Up event

    #if defined(LOGGING)
    //LOGIT( _T("HiddenFrame::MMouseUp event Handler"));
    #endif
    m_MouseEventType = 0;
    m_charMouseEventType = "";

    switch (message)
    {
        case WM_RBUTTONDOWN:
            m_MouseEventType = m_typeRMouseDown;
            m_charMouseEventType = _T("OnRButtonDown");
            break;
        case WM_LBUTTONDOWN:
            m_MouseEventType = m_typeLMouseDown;
            m_charMouseEventType = _T("OnLButtonDown");
            break;
        case WM_MBUTTONDOWN:
            m_MouseEventType = m_typeMMouseDown;
            m_charMouseEventType = _T("OnMButtonDown");
            break;
        case WM_RBUTTONUP:
            m_MouseEventType = m_typeRMouseUp;
            m_charMouseEventType = _T("OnRButtonUp");
            break;
        case WM_LBUTTONUP:
            m_MouseEventType = m_typeLMouseUp;
            m_charMouseEventType = _T("OnLButtonUp");
            break;
        case WM_MBUTTONUP:
            m_MouseEventType = m_typeMMouseUp;
            m_charMouseEventType = _T("OnMButtonUp");
            break;
        default: return false;
    }

    bool rc = false;

    do
    {
        if (not GetRubyClassHandler() )
            break;

        // Translate MS windows mouse flags to Ruby SketchUp flags
        UINT flags = 0;
        flags |= ( (wParam & MK_SHIFT) ? CONSTRAIN_MODIFIER_MASK : 0);
        flags |= ( (wParam & MK_CONTROL) ? COPY_MODIFIER_MASK : 0);
        flags |= ( (GetKeyState(VK_MENU)) ? ALT_MODIFIER_MASK : 0);

        // Translate x,y to client coordinants
        POINT clientPoints;
        clientPoints.x = LOWORD(lParam);
        clientPoints.y = HIWORD(lParam);

        if (g_hWndSketchUpView)
            ScreenToClient( g_hWndSketchUpView, &clientPoints);

        rc = GetRubyClassHandler()->Call_Ruby_OnMouseEventMethod( m_MouseEventType, flags, clientPoints.x, clientPoints.y);

    }while(0);

    if (not rc) do
    {   // user allowed the key
        switch (message)
        {
            case WM_RBUTTONDOWN:
                // not needed. Use WM_MENUSELECT
                //-PostMessage(g_hWndSketchUp, WM_CONTEXTMENU, (WPARAM)g_hWndSketchUp, lParam);
                break;
            case WM_LBUTTONDOWN:
                break;
            case WM_MBUTTONDOWN:
                break;
            case WM_RBUTTONUP:
                break;
            case WM_LBUTTONUP:
                break;
            case WM_MBUTTONUP:
                break;
            default: return false;
        }
    }while(0);

    return rc;
}
    //Parameters doc:
    //
    //    wParam
    //        Indicates whether various virtual keys are down. This parameter can be
    //        one or more of the following values.
    //
    //        MK_CONTROL
    //            The CTRL key is down.
    //        MK_LBUTTON
    //            The left mouse button is down.
    //        MK_MBUTTON
    //            The middle mouse button is down.
    //        MK_RBUTTON
    //            The right mouse button is down.
    //        MK_SHIFT
    //            The SHIFT key is down.
    //        MK_XBUTTON1
    //            Windows 2000/XP: The first X button is down.
    //        MK_XBUTTON2
    //            Windows 2000/XP: The second X button is down.
    //
    //    lParam
    //        The low-order word specifies the x-coordinate of the cursor. The coordinate
    //        is relative to the upper-left corner of the client area.
    //
    //        The high-order word specifies the y-coordinate of the cursor. The
    //        coordinate is relative to the upper-left corner of the client area.
// ----------------------------------------------------------------------------
